<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Demo</title>
</head>

<body onload="initWebGL()">
	<canvas id="my-canvas" width="500" height="500">
		Your browser does not support the HTML5 canvas element.
	</canvas>
</body>
<script id="shader-vs" type="x-shader/x-vertex">
	uniform mat4 uMVMatrix;
	uniform mat4 uPMatrix;

	attribute vec3 aVertexPosition;
	attribute vec4 aVertexColor;
	attribute vec3 aVertexNormal;

	varying highp vec4 vColor;
	varying highp vec3 vPosition;
	varying highp vec3 N;

	void main(void) {
		gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
        
        vColor = aVertexColor;
        vPosition = aVertexPosition;
		N = aVertexNormal; 		
	}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
	uniform highp mat3 uNormalMatrix;
	uniform highp mat4 uMVMatrix;
	uniform highp mat4 uPMatrix;

	varying highp vec4 vColor;
	varying highp vec3 vPosition;
	varying highp vec3 N;

	void main(void) {
        highp vec3 pointLightPosition = vec3(-1.0,3.0,1.0);

        highp vec3 pointLightDirection = vec3(pointLightPosition.xyz - vPosition.xyz);

		highp mat4 mvp = uPMatrix * uMVMatrix;

        highp vec3 L = vec3(mvp * vec4(pointLightDirection, 1.0));
		highp vec3 V = -vec3(mvp * vec4(vPosition,1.0));

   		highp vec3 l = normalize(L);
    	highp vec3 n = normalize(uNormalMatrix * N);
    	highp vec3 v = normalize(V);
		
		highp vec3 R = reflect(l, n);

		highp float diffuseLambert = dot(l,n);
		highp float Roughness = 1.0;
		highp float AmbientIntensity = 0.3;
		highp vec3 DiffuseLightIntensity = vec3(0.9, 0.9, 0.9);
		highp float SpecularIntensity = 0.5;
		highp float shininess = 128.0;

		highp float specular = pow( max(0.0,dot(R,v)), shininess);

		highp vec3 AmbientColour = vec3(0.1, 0.1, 0.1);
		highp vec3 DiffuseMaterialColour = vColor.xyz;
		highp vec3 SpecularColour = vec3(1.0, 1.0, 1.0);
    
	    gl_FragColor = vec4(AmbientColour*AmbientIntensity + 
                        diffuseLambert * DiffuseMaterialColour*DiffuseLightIntensity +
                        SpecularColour * specular*SpecularIntensity, vColor.a);
	}
</script>
<script src="libs/jquery.min.js"></script>
<script src="libs/raf_polyfill.js"></script>
<script src="libs/gl-matrix-min.js"></script>
<script src="libs/webgl-debug.js"></script>
<script src="libs/vector3.js"></script>
<script src="libs/sphere_mesh.js"></script>
<script src="libs/plane_mesh.js"></script>
<script src="libs/SphereObject.js"></script>
<script>
	var gl = null,
		canvas = null,
		program = null,
		fragmentShader = null,
		vertexShader = null;
	
	var vertexPositionAttribute = null,
		vertexNormalAttribute = null,
		vertexColorAttribute = null,
		trianglesVerticeBuffers = [],
		trianglesNormalBuffers = [],
		trianglesColorBuffers = [],
		vertexIndexBuffers = [];
	
	var sceneElements = [];
	var INITIAL_HEIGHT_TRANSLATION_OF_SPHERES = 5.0;
	var GROUND_Y = -1.0;
	var PLANE_SIZE = 10.0;
	var NUM_SPHERES = 60;
	
	var mvMatrix = mat4.create(),
		pMatrix = mat4.create(),
		normalMatrix = mat3.create();
	
	var ELASTICITY = -0.8;
	var paused = false;
	
	$(document).keyup(function(evt){
		switch (evt.keyCode){
			case 80:
				paused = !paused;
				break;
			default:
				break;
		}
	});
	
	function initShaders()
	{
		vertexShader = makeShader($('#shader-vs').html(), gl.VERTEX_SHADER);
		fragmentShader = makeShader($('#shader-fs').html(), gl.FRAGMENT_SHADER);
		
		//create program
		program = gl.createProgram();
		
		//attach and link shaders to the program
		gl.attachShader(program, vertexShader);
		gl.attachShader(program, fragmentShader);
		gl.linkProgram(program);

		if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
			alert("Unable to initialize the shader program.");
		}
		//use program
		gl.useProgram(program);
	}
	
	function makeShader(src, type)
	{
		//compile the vertex shader
		var shader = gl.createShader(type);
		gl.shaderSource(shader, src);
		gl.compileShader(shader);

		if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
			alert("Error compiling shader: " + gl.getShaderInfoLog(shader));
		}
		return shader;
	}
			
	function setupWebGL()
	{
		//set the clear color to a shade of green
		gl.clearColor(0.5, 0.5, 0.5, 1.0); 	
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 	
		gl.enable(gl.DEPTH_TEST);
	}
	
	function setupBuffers()
	{
		vertexPositionAttribute = gl.getAttribLocation(program, "aVertexPosition");
		vertexColorAttribute = gl.getAttribLocation(program, "aVertexColor");
		vertexNormalAttribute = gl.getAttribLocation(program, "aVertexNormal");
		gl.enableVertexAttribArray(vertexPositionAttribute);
		gl.enableVertexAttribArray(vertexColorAttribute);
		gl.enableVertexAttribArray(vertexNormalAttribute);
	}
	
	function getMatrixUniforms(){
		program.pMatrixUniform = gl.getUniformLocation(program, "uPMatrix");
		program.mvMatrixUniform = gl.getUniformLocation(program, "uMVMatrix");          
		program.normalMatrixUniform = gl.getUniformLocation(program, "uNormalMatrix");          
    }
			
	function setMatrixUniforms() {
		gl.uniformMatrix4fv(program.mvMatrixUniform, false, mvMatrix);
        gl.uniformMatrix3fv(program.normalMatrixUniform, false, normalMatrix);
	}
	
	function searchForObject(arr, index){
		for (var i in arr){
			if (arr[i].vbo_index == index){
				return i;
			}
		}
		return -1;
	}
	
	function checkForCollisions(arr, n)
	{				
		for(var i in arr)
		{
			if(i != n)
			{
				var p1 = arr[n].position;
				var p2 = arr[i].position;
				var v = new Vector3( p1.x - p2.x, p1.y - p2.y, p1.z - p2.z );
				
				if(v.length() < (2.0 * arr[n].radius) )
				{
					var tmp = arr[n].velocity;
					arr[n].velocity = arr[i].velocity;
					arr[i].velocity = tmp;

					arr[n].position.x += arr[n].velocity.x;
					arr[n].position.y += arr[n].velocity.y;
					//arr[n].position.z += arr[n].velocity.z;

					arr[i].position.x += arr[i].velocity.x;
					arr[i].position.y += arr[i].velocity.y;
					//arr[i].position.z += arr[i].velocity.z;
				}	
			}
		}
	}
	
	function drawScene(){
		for (var i=0;i<vertexIndexBuffers.length;i++){
			mat4.identity(mvMatrix);
			mat4.translate(mvMatrix, [0.0, -1.0, -15.5]);
			mat4.rotate(mvMatrix, -0.3, [-0.3, 0.0, 0.2]);
			mat4.rotate(mvMatrix, 90, [0.0, 1.0, 0.0]);
			
			var n = searchForObject(sceneElements, i);
			if (n != -1){
				if (INITIAL_HEIGHT_TRANSLATION_OF_SPHERES - sceneElements[n].position.y - sceneElements[n].radius > GROUND_Y)
				{
					sceneElements[n].velocity.y += sceneElements[n].acceleration.y;
					sceneElements[n].position.y += sceneElements[n].velocity.y;
				} else {
					sceneElements[n].position.y -= sceneElements[n].velocity.y;
					sceneElements[n].velocity.y *= ELASTICITY;
				}
				
				if (sceneElements[n].position.x > PLANE_SIZE || sceneElements[n].position.x < -PLANE_SIZE){
					sceneElements[n].velocity.x *= -1.0;
					sceneElements[n].position.x += sceneElements[n].velocity.x;
				} else {
					sceneElements[n].position.x += sceneElements[n].velocity.x;
				}
				
				if (sceneElements[n].position.z > PLANE_SIZE || sceneElements[n].position.z < -PLANE_SIZE){
					sceneElements[n].velocity.z *= -1.0;
					sceneElements[n].position.z += sceneElements[n].velocity.z;
				} else {
					sceneElements[n].position.z += sceneElements[n].velocity.z;
				}
				checkForCollisions(sceneElements, n);
				
				mat4.translate(mvMatrix, [
					sceneElements[n].position.x,
					INITIAL_HEIGHT_TRANSLATION_OF_SPHERES - sceneElements[n].position.y,
					sceneElements[n].position.z
				]);
			}
			mat4.toInverseMat3(mvMatrix, normalMatrix);
			mat3.transpose(normalMatrix);
			setMatrixUniforms();
			
			gl.bindBuffer(gl.ARRAY_BUFFER, trianglesVerticeBuffers[i]);
			gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
			
			gl.bindBuffer(gl.ARRAY_BUFFER, trianglesColorBuffers[i]);
			gl.vertexAttribPointer(vertexColorAttribute, 4, gl.FLOAT, false, 0, 0);
			
			gl.bindBuffer(gl.ARRAY_BUFFER, trianglesNormalBuffers[i]);
			gl.vertexAttribPointer(vertexNormalAttribute, 3, gl.FLOAT, false, 0, 0);
			
			gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vertexIndexBuffers[i]);
			gl.drawElements(gl.TRIANGLES, vertexIndexBuffers[i].numItems, gl.UNSIGNED_SHORT, 0);
		}
	}
	
	function initWebGL(){
		canvas = document.getElementById("my-canvas");
		try
		{
			gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
		}
		catch (e){}
		if (gl){
			gl = WebGLDebugUtils.makeDebugContext(gl);
			initShaders();
			setupPlaneMesh(0, {"translation": [0.0, GROUND_Y, 0.0]});
			for (var i=1;i<=NUM_SPHERES;i++){
				var radius = 0.5;
				var s = new SphereObject({
					"vbo_index": i,
					"radius": radius,
					"position": new Vector3(10.0 * Math.random() - 5.0, -5.0 * Math.random(), 10.0 * Math.random() - 5.0),
					"velocity": new Vector3(.1 * (Math.random()-0.5), 0.0, .1*(Math.random()-0.5)),
					"acceleration": new Vector3(0.0, 0.01, 0.0)
				});
				sceneElements.push(s);
				setupSphereMesh(i, {
					"color": [Math.random(), Math.random(), Math.random(), 1.0],
					"radius": radius
				});
			}
			setupPlaneMesh(0, { "translation": [0.0, GROUND_Y, 0.0] });
			getMatrixUniforms();
			setupBuffers();
			
			gl.viewport(0, 0, canvas.width, canvas.height);
			mat4.perspective(45, canvas.width / canvas.height, 0.1, 100.0, pMatrix);
			gl.uniformMatrix4fv(program.pMatrixUniform, false, pMatrix);
			
			(function animLoop(){
				if (!paused){
					setupWebGL();
					drawScene();
				}
				requestAnimationFrame(animLoop, canvas);
			})();
		} else {
			alert("no support");
		}
	}
</script>
</html>
